package org.gwt.grin.rebind;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.util.List;

import org.gwt.grin.client.Feature;
import org.gwt.grin.client.GrinCommands;
import org.gwt.grin.client.Segment;
import org.gwt.grin.client.commands.Command;
import org.gwt.grin.client.input.RCHandler;
import org.gwt.grin.client.util.Color;
import org.gwt.grin.client.util.FontSpec;
import org.gwt.grin.client.util.Rectangle;
import org.gwt.grin.client.util.ShowBuilderHelper;
import org.gwt.grin.rebind.input.SERCHandler;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dom.client.Style;
import com.google.gwt.uibinder.rebind.IndentedWriter;
import com.google.gwt.uibinder.rebind.MortalLogger;
import com.google.gwt.user.client.ui.HasAlignment;

/**
 * Writer for GWT Grin generated classes.
 * 
 * @author ggeorg
 */
public class GrinWriter {

	/**
	 * The type we have been asked to generated, e.g. MyGrinCommands
	 */
	private final JClassType baseClass;

	/**
	 * The name of the class we're creating, e.g. MyGrinCommandsImpl
	 */
	private final String implClassName;

	private final String grinFile;
	private final TypeOracle oracle;
	private final MortalLogger logger;

	private JClassType directorType;
	private JClassType clientBundleType;

	public GrinWriter(JClassType baseClass, String implClassName,
			String grinFile, TypeOracle oracle, MortalLogger logger)
			throws UnableToCompleteException {
		this.baseClass = baseClass;
		this.implClassName = implClassName;
		this.oracle = oracle;
		this.logger = logger;
		this.grinFile = grinFile;
		// this.fieldManager = fieldManager;
		// this.messages = messagesWriter;

		// Check for possible misuse 'GWT.create(GrinCommands.class)'
		JClassType grinCommandsItself = oracle.findType(GrinCommands.class
				.getCanonicalName());
		if (grinCommandsItself.equals(baseClass)) {
			die("You must use a subtype of GrinCommands in GWT.create(). E.g.,\n"
					+ " interface MyGrinCommands extends GrinCommands {}\n"
					+ " GWT.create(MyGrinCommands.class);");
		}

		JClassType[] grinCommandsTypes = baseClass.getImplementedInterfaces();
		if (grinCommandsTypes.length == 0) {
			throw new RuntimeException("No implemented interfaces for "
					+ baseClass.getName());
		}
		JClassType grinCommandsType = grinCommandsTypes[0];

		JClassType[] typeArgs = grinCommandsType.isParameterized()
				.getTypeArgs();
		if (typeArgs.length < 2) {
			throw new RuntimeException(
					"Director type parameter is required for type %s"
							+ grinCommandsType.getName());
		}
		directorType = typeArgs[0];
		clientBundleType = typeArgs[1];
	}

	/**
	 * Entry point for the code generation logic.
	 * 
	 * @param reader
	 * @param printWriter
	 */
	public void parse(Reader reader, PrintWriter printWriter)
			throws UnableToCompleteException {
		ShowParser showParser = new ShowParser(reader, grinFile,
				getExtensionParser(), logger);
		try {
			showParser.parse();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		StringWriter stringWriter = new StringWriter();
		IndentedWriter niceWriter = new IndentedWriter(new PrintWriter(
				stringWriter));
		writeShowBuilder(niceWriter, showParser.getBuilder());

		printWriter.print(stringWriter.toString());
	}

	protected ExtensionParser getExtensionParser() {
		return null;
	}

	/**
	 * Post an error message and halt processing. This method always throws an
	 * {@link UnableToCompleteException}
	 */
	public void die(String message) throws UnableToCompleteException {
		logger.die(message);
	}

	private void writeShowBuilder(IndentedWriter w, ShowBuilder builder)
			throws UnableToCompleteException {
		writePackage(w);

		writeImports(w);
		w.newline();

		writeClassOpen(w);

		w.write("private static Show showInstance = null;");
		w.write("private static %s clientBundle = GWT.create(%s.class);",
				clientBundleType.getQualifiedSourceName(),
				clientBundleType.getQualifiedSourceName());

		// buildShow method
		w.newline();
		w.write("public Show buildShow(final %s director) {",
				directorType.getParameterizedQualifiedSourceName());
		w.indent();
		w.write("if(showInstance !=null) {");
		w.indent();
		w.write("return showInstance;");
		w.outdent();
		w.write("}");

		w.newline();
		w.write("showInstance = new Show(director);");
		w.newline();

		writeAllCommands(w, builder);
		writeAllFeatures(w, builder);
		writeAllRCHandlers(w, builder);
		writeAllSegments(w, builder);

		setupAllCommands(w, builder);
		setupAllFeatures(w, builder);
		setupAllRCHandlers(w, builder);
		// TODO setupAllSegments(w, builder);

		w.write("showInstance.buildShow(segments, features, rcHandlers, null);");

		w.write("return showInstance;");
		w.outdent();
		w.write("}");

		// getDirector method
		w.newline();
		w.write("public %s getDirector() {",
				directorType.getParameterizedQualifiedSourceName());
		w.indent();
		w.write("assert showInstance != null;");
		w.write("return (%s) showInstance.getDirector();",
				directorType.getParameterizedQualifiedSourceName());
		w.outdent();
		w.write("}");

		// getClientBundle method
		w.newline();
		w.write("public %s getClientBundle() {",
				clientBundleType.getParameterizedQualifiedSourceName());
		w.indent();
		w.write("assert clientBundle != null;");
		w.write("return clientBundle;");
		w.outdent();
		w.write("}");

		// close class
		w.outdent();
		w.write("}");
	}
	
	private void setupAllRCHandlers(IndentedWriter w, ShowBuilder builder) {
		List<SERCHandler> rcHandlers = builder.allRCHandlers;
		for (SERCHandler h : rcHandlers) {
			h.setup(w, builder);
		}
		w.newline();
	}

	private void setupAllFeatures(IndentedWriter w, ShowBuilder builder) {
		List<SEFeature> features = builder.allFeatures;
		for (SEFeature f : features) {
			f.setup(w, builder);
		}
		w.newline();
	}

	private void setupAllCommands(IndentedWriter w, ShowBuilder builder) {
		List<SECommand> commands = builder.allCommands;
		for (SECommand c : commands) {
			c.setup(w, builder);
		}
		w.newline();
	}

	private void writeAllCommands(IndentedWriter w, ShowBuilder builder) {
		List<SECommand> commands = builder.allCommands;
		String cn = Command.class.getName();
		w.write("%s[] commands = new %s[%d];", cn, cn, commands.size());
		w.newline();
		for (int i = 0, n = commands.size(); i < n; i++) {
			SECommand c = commands.get(i);
			String vn = c.write(w, builder, i);
			w.write("commands[%d] = %s;", i, vn);
			w.newline();
		}
	}

	private void writeAllSegments(IndentedWriter w, ShowBuilder builder) {
		List<SESegment> segments = builder.allSegments;
		String cn = Segment.class.getName();
		w.write("%s[] segments = new %s[%d];", cn, cn, segments.size());
		w.newline();
		for (int i = 0, n = segments.size(); i < n; i++) {
			SENode s = segments.get(i);
			String vn = s.write(w, builder, i);
			w.write("segments[%d] = %s;", i, vn);
			w.newline();
		}
	}

	private void writeAllFeatures(IndentedWriter w, ShowBuilder builder) {
		List<SEFeature> features = builder.allFeatures;
		String cn = Feature.class.getName();
		w.write("%s[] features = new %s[%d];", cn, cn, features.size());
		w.newline();
		for (int i = 0, n = features.size(); i < n; i++) {
			SENode f = features.get(i);
			String vn = f.write(w, builder, i);
			w.write("features[%d] = %s;", i, vn);
			w.newline();
		}
	}
	
	private void writeAllRCHandlers(IndentedWriter w, ShowBuilder builder) {
		List<SERCHandler> rcHandlers = builder.allRCHandlers;
		String cn = RCHandler.class.getName();
		w.write("%s[] rcHandlers = new %s[%d];", cn, cn, rcHandlers.size());
		w.newline();
		for (int i = 0, n = rcHandlers.size(); i < n; i++) {
			SERCHandler h = rcHandlers.get(i);
			String vn = h.write(w, builder, i);
			w.write("rcHandlers[%d] = %s;", i, vn);
			w.newline();
		}
	}

	private void writePackage(IndentedWriter w) {
		String packageName = baseClass.getPackage().getName();
		if (packageName.length() > 0) {
			w.write("package %1$s;", packageName);
			w.newline();
		}
	}

	private void writeImports(IndentedWriter w) {
		w.write("import %s;", GWT.class.getName());
		w.write("import %s;", Style.class.getName());
		w.write("import %s;", HasAlignment.class.getName());
		w.write("import %s;", Feature.class.getName());
		w.write("import %s;", Segment.class.getName());
		w.write("import %s;", RCHandler.class.getName());
		w.write("import %s;", Command.class.getName());
		w.write("import %s;", ShowBuilderHelper.class.getName());
		w.write("import %s.%s;", directorType.getPackage().getName(),
				directorType.getName());
		w.write("import %s.%s;", clientBundleType.getPackage().getName(),
				clientBundleType.getName());
		w.write("import %s;", Color.class.getName());
		w.write("import %s;", FontSpec.class.getName());
		w.write("import %s;", Rectangle.class.getName());
	}

	private void writeClassOpen(IndentedWriter w) {
		String s = "public class %s extends ShowBuilderHelper implements %s {";
		w.write(s, implClassName,
				baseClass.getParameterizedQualifiedSourceName());
		w.indent();
	}

}
